/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include <string.h>
#include <math.h>
#include "../common/cmdlib.h"
#include "../common/mathlib.h"
#include "../common/imagelib.h"
#include "../common/scriplib.h"

#ifdef _TTIMOBUILD
#include "../common/qfiles.h"
#include "../common/surfaceflags.h"
#else
#include "../common/qfiles.h"
#include "../common/surfaceflags.h"
#endif

#include "shaders.h"
#ifdef _WIN32

#ifdef _TTIMOBUILD
#include "pakstuff.h"
#include "jpeglib.h"
#else
#include "../libs/pakstuff.h"
#include "../libs/jpeglib.h"
#endif

#endif


#define	MAX_SURFACE_INFO	32768

shaderInfo_t	**terrainShaderTable;

shaderInfo_t	defaultInfo;
shaderInfo_t	shaderInfo[MAX_SURFACE_INFO];
int				numShaderInfo;



typedef struct {
	char	*name;
	int		surfType;
} surfTypeParm_t;

surfTypeParm_t	surfTypeParms[] = {
	{"grass",			SURFTYPE_GRASS},
	{"moss",			SURFTYPE_MOSS},
	{"rock",			SURFTYPE_ROCK},
	{"gravel",			SURFTYPE_GRAVEL},
	{"dirt",			SURFTYPE_DIRT},
	{"mud",				SURFTYPE_MUD},
	{"sand",			SURFTYPE_SAND},
	{"snow",			SURFTYPE_SNOW},
	{"ice",				SURFTYPE_ICE},
	{"liquid",			SURFTYPE_LIQUID},
	{"flesh",			SURFTYPE_FLESH},
	{"armor",			SURFTYPE_ARMOR},
	{"metal",			SURFTYPE_METAL},
	{"concrete",		SURFTYPE_CONCRETE},
	{"brick",			SURFTYPE_BRICK},
	{"plaster",			SURFTYPE_PLASTER},
	{"clay",			SURFTYPE_CLAY},
	{"marble",			SURFTYPE_MARBLE},
	{"ceramic",			SURFTYPE_CERAMIC},
	{"asphalt",			SURFTYPE_ASPHALT},
	{"wood",			SURFTYPE_WOOD},
	{"glass",			SURFTYPE_GLASS},
	{"bpGlass",			SURFTYPE_BPGLASS},
	{"plastic",			SURFTYPE_PLASTIC},
	{"rubber",			SURFTYPE_RUBBER},
	{"foam",			SURFTYPE_FOAM},
	{"cardboard",		SURFTYPE_CARDBOARD},
	{"carpet",			SURFTYPE_CARPET},
	{"cloth",			SURFTYPE_CLOTH},
	{"electronic",		SURFTYPE_ELECTRONIC},
	{"forceField",		SURFTYPE_FORCEFIELD}
};

typedef struct {
	char	*name;
	int		clearSolid, surfaceFlags, contents;
} infoParm_t;

infoParm_t	infoParms[] = {
	{"hint",			0,	SURFACE_HINT,		0},
	{"discrete",		0,	SURFACE_DISCRETE,	0},
	{"portalSky",		0,	SURFACE_PORTALSKY,	0},
	{"slick",			0,	SURFACE_SLICK,		0},
	{"bounce",			0,	SURFACE_BOUNCE,		0},
	{"ladder",			0,	SURFACE_LADDER,		0},
	{"noDamage",		0,	SURFACE_NODAMAGE,	0},
	{"noImpact",		0,	SURFACE_NOIMPACT,	0},
	{"noSteps",			0,	SURFACE_NOSTEPS,	0},
	{"noClip",			0,	SURFACE_NOCLIP,		0},
	{"noDraw",			0,	SURFACE_NODRAW,		0},
	{"noTJunc",			0,	SURFACE_NOTJUNC,	0},

	{"nonSolid",		1,	0,					0}, 
	{"solid",			0,	0,					CONTENT_SOLID},
	{"translucent",		0,	0,					CONTENT_TRANSLUCENT},
	{"water",			1,	0,					CONTENT_WATER},
	{"slime",			1,	0,					CONTENT_SLIME},
	{"lava",			1,	0,					CONTENT_LAVA},
	{"playerClip",		1,	0,					CONTENT_PLAYERCLIP},
	{"vehicleClip",		1,	0,					CONTENT_VEHICLECLIP},
	{"moveableClip",	1,	0,					CONTENT_MOVEABLECLIP},
	{"missileClip",		1,	0,					CONTENT_MISSILECLIP},
	{"fullClip",		1,	0,					CONTENT_PLAYERCLIP | CONTENT_VEHICLECLIP | CONTENT_MOVEABLECLIP | CONTENT_MISSILECLIP},
	{"detail",			0,	0,					CONTENT_DETAIL},
	{"structural",		0,	0,					CONTENT_STRUCTURAL},
	{"areaPortal",		1,	0,					CONTENT_AREAPORTAL},
	{"noDrop",			1,	0,					CONTENT_NODROP}
};



/*
===============
LoadShaderImage
===============
*/

byte* LoadImageFile(char *filename)
{
  byte *buffer = NULL;

  if (FileExists(filename))
  {
    LoadFileBlock(filename, &buffer);
  }
#ifdef _WIN32
  else
  {
    PakLoadAnyFile(filename, &buffer);
  }
#endif

  return buffer;
}

/*
===============
LoadShaderImage
===============
*/
static void LoadShaderImage( shaderInfo_t *si ) {
	char			filename[1024];
  byte      *buffer, *pixels;

	// look for the editorimage if it is specified
	if ( si->editorimage[0] ) {
		sprintf( filename, "%s%s", gamedir, si->editorimage );
		DefaultExtension( filename, ".tga" );
    buffer = LoadImageFile(filename);
    if ( buffer != NULL) {
      goto loadTga;
    }
  }

  // just try the shader name with a _d.tga
	// on unix, we have case sensitivity problems...
  sprintf( filename, "%s%s_d.tga", gamedir, si->shader );
  buffer = LoadImageFile(filename);
  if ( buffer != NULL) {
		goto loadTga;
	}

  sprintf( filename, "%s%s_d.TGA", gamedir, si->shader );
  buffer = LoadImageFile(filename);
  if ( buffer != NULL) {
		goto loadTga;
	}


  // just try the shader name with a .tga
	// on unix, we have case sensitivity problems...
  sprintf( filename, "%s%s.tga", gamedir, si->shader );
  buffer = LoadImageFile(filename);
  if ( buffer != NULL) {
		goto loadTga;
	}

  sprintf( filename, "%s%s.TGA", gamedir, si->shader );
  buffer = LoadImageFile(filename);
  if ( buffer != NULL) {
		goto loadTga;
	}

	// couldn't load anything
	_printf("WARNING: Couldn't find image for shader %s\n", si->shader );

	si->width = 64;
	si->height = 64;
	return;

	// load the image to get dimensions and color
loadTga:
  LoadTGABuffer( buffer, &pixels, &si->width, &si->height );

  free(buffer);
  free(pixels);
}

/*
===============
AllocShaderInfo
===============
*/
static shaderInfo_t	*AllocShaderInfo( void ) {
	shaderInfo_t	*si;

	if ( numShaderInfo == MAX_SURFACE_INFO ) {
		Error( "MAX_SURFACE_INFO" );
	}
	si = &shaderInfo[ numShaderInfo ];
	numShaderInfo++;

	// set defaults
	memset(si, 0, sizeof(shaderInfo_t));

	si->contents = CONTENT_SOLID | CONTENT_OPAQUE;

	return si;
}

/*
===============
ShaderInfoForShader
===============
*/
shaderInfo_t	*ShaderInfoForShader( const char *shaderName ) {
	int				i;
	shaderInfo_t	*si;
	char			shader[256];

	// strip off extension
	strcpy( shader, shaderName );
	StripExtension( shader );

	// search for it
	for ( i = 0 ; i < numShaderInfo ; i++ ) {
		si = &shaderInfo[ i ];
		if ( !Q_stricmp( shader, si->shader ) ) {
			if ( !si->width ) {
				LoadShaderImage( si );
			}
			return si;
		}
	}

	si = AllocShaderInfo();
	strcpy( si->shader, shader );

	LoadShaderImage( si );

	return si;
}

/*
===============
ShaderInfoForTerrain
===============
*/
shaderInfo_t *ShaderInfoForTerrain (shaderInfo_t *si){

	int		index = si->surfaceFlags & SURFTYPE_MASK;

	return terrainShaderTable[index];
}

/*
===============
ParseShaderFile
===============
*/
static void ParseShaderFile( const char *filename ) {
	int		i;
	int		numSurfTypeParms = sizeof(surfTypeParms) / sizeof(surfTypeParms[0]);
	int		numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]);
	shaderInfo_t	*si;

//	qprintf( "shaderFile: %s\n", filename );
	LoadScriptFile( filename );
	while ( 1 ) {
		if ( !GetToken( qtrue ) ) {
			break;
		}

		// Skip tables
		if (!stricmp(token, "table")){
			qboolean	done = qfalse;
			int			depth = 0;

			// Parse the name
			if (!GetToken(qtrue))
				break;

			// Parse the definition
			do {
				if (!GetToken(qtrue)){
					done = qtrue;
					break;
				}

				if (!stricmp(token, "{"))
					depth++;
				else if	(!stricmp(token, "}"))
					depth--;
			} while (depth);

			if (done)
				break;

			continue;
		}

		si = AllocShaderInfo();
		strcpy( si->shader, token );
		MatchToken( "{" );
		while ( 1 ) {
			if ( !GetToken( qtrue ) ) {
				break;
			}
			if ( !strcmp( token, "}" ) ) {
				break;
			}

			// skip internal braced sections
			if ( !strcmp( token, "{" ) ) {
				while ( 1 ) {
					if ( !GetToken( qtrue ) ) {
						break;
					}
					if ( !strcmp( token, "}" ) ) {
						break;
					}
				}
				continue;
			}

			// qer_editorImage <image>
			if ( !Q_stricmp( token, "qer_editorImage" ) ) {
				GetToken( qfalse );
				strcpy( si->editorimage, token );
				DefaultExtension( si->editorimage, ".tga" );
				continue;
			}

			// origin
			if ( !Q_stricmp( token, "origin" ) ) {
				si->origin = qtrue;
				continue;
			}

			// foliage <model> <minDistance> <maxDistance> <density> <odds> <inverseAlpha>
			if ( !Q_stricmp( token, "foliage" ) ) {
				foliage_t	*foliage;

				foliage = malloc(sizeof(foliage_t));
				foliage->next = si->foliage;
				si->foliage = foliage;

				GetToken( qfalse );
				strcpy(foliage->model, token);
				GetToken( qfalse );
				foliage->minDistance = atof(token);
				GetToken( qfalse );
				foliage->maxDistance = atof(token);
				GetToken( qfalse );
				foliage->density = atof(token);
				GetToken( qfalse );
				foliage->odds = atof(token);
				GetToken( qfalse );
				foliage->inverseAlpha = atoi(token);
				continue;
			}

			// tessSize <size>
			if ( !Q_stricmp( token, "tessSize" ) ) {
				GetToken( qfalse );
				si->subdivisions = atof(token);
				continue;
			}

			// backMaterial <material>
			if ( !Q_stricmp( token, "backMaterial" ) ) {
				GetToken( qfalse );
				strcpy(si->backMaterial, token);
				StripExtension(si->backMaterial);
				continue;
			}

			// deform <function> [parms...]
			// We catch these so we can try to hack the surface bounds to prevent
			// them from being culled out in game.
			// In most cases, this is far from perfect (although it usually helps
			// a bit, unless doing extreme deforms), but in certain specific cases
			// we can calculate the proper bounds and avoid visual glitches (ie: surfaces
			// suddenly disappearing as the camera moves when they should still be visible).
			if ( !Q_stricmp( token, "deform" ) ) {
				GetToken( qfalse );

				if (!Q_stricmp(token, "expand")){
					si->deform = DFRM_EXPAND;

					// We rely on this being a constant to get it right
					GetToken( qfalse );
					si->deformSize = atof(token);

					if (!si->deformSize)
						si->deformSize = 64.0f;		// It is likely a variable
				}
				else if (!Q_stricmp(token, "move")){
					si->deform = DFRM_MOVE;
					si->deformSize = 64.0f;			// Bah...
				}
				else if (!Q_stricmp(token, "sprite"))
					si->deform = DFRM_SPRITE;
				else if (!Q_stricmp(token, "tube"))
					si->deform = DFRM_TUBE;
				else if (!Q_stricmp(token, "flare")){
					si->deform = DFRM_FLARE;

					// We rely on this being a constant to get it right
					GetToken( qfalse );
					si->deformSize = atof(token);

					if (!si->deformSize)
						si->deformSize = 64.0f;		// It is likely a variable
				}

				// Ignore all other tokens on the line
				while (TokenAvailable()){
					si->deformSize = 64.0f;			// It was an expression

					GetToken(qfalse);
				}

				continue;
			}

			// Surface types
			for ( i = 0 ; i < numSurfTypeParms ; i++ ) {
				if ( !Q_stricmp( token, surfTypeParms[i].name ) ) {
					si->surfaceFlags |= surfTypeParms[i].surfType;
					break;
				}
			}
			if ( i != numSurfTypeParms ) {
				continue;
			}

			// Surface parms
			for ( i = 0 ; i < numInfoParms ; i++ ) {
				if ( !Q_stricmp( token, infoParms[i].name ) ) {
					si->surfaceFlags |= infoParms[i].surfaceFlags;
					si->contents |= infoParms[i].contents;

					if ( infoParms[i].clearSolid ) {
						si->contents &= ~CONTENT_SOLID;
					}

					// translucent overrides opaque
					if (si->contents & CONTENT_TRANSLUCENT)
						si->contents &= ~CONTENT_OPAQUE;

					// slime overrides water
					if (si->contents & CONTENT_SLIME)
						si->contents &= ~CONTENT_WATER;

					// lava overrides water and slime
					if (si->contents & CONTENT_LAVA)
						si->contents &= ~(CONTENT_WATER | CONTENT_SLIME);

					// structural overrides detail
					if (si->contents & CONTENT_STRUCTURAL)
						si->contents &= ~CONTENT_DETAIL;

					break;
				}
			}
			if ( i != numInfoParms ) {
				continue;
			}

			// ignore all other tokens on the line
			while ( TokenAvailable() ) {
				GetToken( qfalse );
			}
		}			
	}
}

/*
===============
CreateTerrainShaders

Create a terrain shader for each surfType available, so that we can replace
textures/common/terrain with a shader that has the same surfType used in the
visible shader, which means the collision code will take surfTypes into account
===============
*/
static void CreateTerrainShaders (void){

	shaderInfo_t	*terrain, *si;
	int				numSurfTypeParms = sizeof(surfTypeParms) / sizeof(surfTypeParms[0]);
	int				i;

	terrainShaderTable = malloc(numSurfTypeParms * sizeof(shaderInfo_t *));

	// Be careful... all generated shaders must be exactly the same as
	// textures/common/terrain for things to work properly. We will just
	// add the surfType parameter and change the name.
	terrain = ShaderInfoForShader("textures/common/terrain");

	terrainShaderTable[0] = terrain;

	for (i = 1; i < numSurfTypeParms; i++){
		si = AllocShaderInfo();

		memcpy(si, terrain, sizeof(shaderInfo_t));

		sprintf(si->shader, "textures/common/terrain_%s", surfTypeParms[i].name);
		si->surfaceFlags |= surfTypeParms[i].surfType;

		// Store it in the table
		terrainShaderTable[i] = si;
	}
}

/*
===============
LoadShaderInfo
===============
*/
#define	MAX_SHADER_FILES	512
void LoadShaderInfo( void ) {
	char			filename[1024];
	int				i;
	char			*shaderFiles[MAX_SHADER_FILES];
	int				numShaderFiles = 0;

#ifdef _WIN32

	WIN32_FIND_DATA	findInfo;
	HANDLE			findHandle;
	BOOL			findRes = TRUE;

	sprintf( filename, "%smaterials/*", gamedir );

	findHandle = FindFirstFile(filename, &findInfo);
	if (findHandle == INVALID_HANDLE_VALUE){
		qprintf( "%5i shaderInfo\n", 0);
		return;
	}

	while (findRes == TRUE){
		// Check for invalid file name
		if (!stricmp(findInfo.cFileName, ".") || !stricmp(findInfo.cFileName, "..")){
			findRes = FindNextFile(findHandle, &findInfo);
			continue;
		}

		// Add it to the list
		if (numShaderFiles == MAX_SHADER_FILES)
			break;

		if (!(findInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
			if (!stricmp(&findInfo.cFileName[strlen(findInfo.cFileName)-4], ".mtr")){
				shaderFiles[numShaderFiles] = malloc(MAX_OS_PATH);
				strcpy(shaderFiles[numShaderFiles], findInfo.cFileName);
				numShaderFiles++;
			}
		}

		findRes = FindNextFile(findHandle, &findInfo);
	}

	FindClose(findHandle);

	for ( i = 0 ; i < numShaderFiles ; i++ ) {
		sprintf( filename, "%smaterials/%s", gamedir, shaderFiles[i] );
		ParseShaderFile( filename );
	    free(shaderFiles[i]);
	}

	if (numShaderFiles){
		qprintf( "%5i shaderInfo\n", numShaderInfo);

		// Create the terrain shaders
		CreateTerrainShaders();
		return;
	}

#endif

	sprintf( filename, "%smaterials/materiallist.txt", gamedir );
	LoadScriptFile( filename );

	while ( 1 ) {
		if ( !GetToken( qtrue ) ) {
			break;
		}
    shaderFiles[numShaderFiles] = malloc(MAX_OS_PATH);
		strcpy( shaderFiles[ numShaderFiles ], token );
		numShaderFiles++;
	}

	for ( i = 0 ; i < numShaderFiles ; i++ ) {
		sprintf( filename, "%smaterials/%s.mtr", gamedir, shaderFiles[i] );
		ParseShaderFile( filename );
    free(shaderFiles[i]);
	}

	qprintf( "%5i shaderInfo\n", numShaderInfo);

	// Create the terrain shaders
	CreateTerrainShaders();
}

